/* 
 * File:   server_FileService.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 22, 2012, 4:58 AM
 */
#include <iomanip>
#include <string.h>
#include <sstream>
#include <fstream>
#include <vector>
#include <arpa/inet.h>
#include <cmath>
#include <stdint.h>
#include <stdlib.h>
#include <cstdio>
#include "server_FileService.h"
#include "common_ProtocoloConstantes.h"
#include "common_Codec.h"
#include "server_Proceso.h"
#include "common_MutexScopeLocker.h"


using namespace std;
using namespace TP;
using namespace TP::Servicios;

#define COMANDO_INVALIDO 0
#define IP_INVALIDA 0

#define FILE_SERVICE_NAME "FILE_SERVICE"

#define FS_DECLARE_FILE "DECLARE_FILE"
#define FS_PUT_CHUNK    "PUT_CHUNK"
#define FS_DEFINE_FILE  "DEFINE_FILE"


FileService::FileService(const string& pPathAu) : Service(FILE_SERVICE_NAME) {
    _pathAu = pPathAu;
}
        
FileService::~FileService() {}

void FileService::Iniciar() throw (Excepcion&) {}

void FileService::Finalizar() throw (Excepcion&) {}

void FileService::mensajeEntranteManejador(IMensajero* pMensajero, const string & pMensaje) {
    try
    {
        // <token>|<FILE_SERVICE>|<Metodo>|<args>
        
        hidratarMensaje(pMensaje);
        validarIntegridadMensaje();
        
        if (getNombre() != getMensaje()[1]) return;
        
        // Se determina a que accion corresponde el mensaje.
        string metodo = getMensaje()[2];

        if (metodo == FS_DECLARE_FILE)
        {
            this->declareFile(pMensajero, getMensaje());
        }
        else if (metodo == FS_PUT_CHUNK)
        {
            this->putFileChunk(pMensajero, getMensaje());
        }
        else if (metodo == FS_DEFINE_FILE)
        {
            this->defineFile(pMensajero, getMensaje());
        }
        else
        {
            // Metodo invalido
            throw 1;
        }
    }
    catch (int &pEx)
    {
        try
        {
            string respuesta = getMensaje()[0];
            respuesta += SEPARADOR_CAMPO;
            respuesta += ERROR;
            respuesta += SEPARADOR_CAMPO;
            respuesta += "Comando invalido.";
            
            pMensajero->Responder(respuesta);
        }
        catch (Excepcion& pEx)
        {
            cerr << pEx.what() << endl;
        }
    }
}

void FileService::responderEco(IMensajero* pMensajero, vector<string> &pComando) {
    pMensajero->Responder(OK);
}

void FileService::declareFile(IMensajero* pMensajero, vector<string>& pComando) {
    
    // <token>|<FILE_SERVICE>|<DECLARE_FILE>|<file_name>
    
    string fileName = pComando[3];
    
    // Si el archivo ya existe, no se puede declararlo.
    std::ifstream file((_pathAu + string("/") + fileName).c_str());
    std::ifstream fileTmp((_pathAu + string("/") + fileName + ".tmp").c_str());
    
    if (file || fileTmp) {
        
        file.close();
        fileTmp.close();
        
        // Enviar respuesta error al cliente
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FILE_SERVICE_NAME;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FS_DECLARE_FILE;
        respuesta += SEPARADOR_CAMPO;
        respuesta += ERROR;
        respuesta += SEPARADOR_CAMPO;
        respuesta += "Archivo existente.";
        
        pMensajero->Responder(respuesta);
        
        return;
    }    
    
    // Creamos el archivo
    string fullPath = (_pathAu + string("/") + fileName + string(".tmp")).c_str();
    
    std::ofstream newFile(fullPath.c_str(), std::ios_base::binary | ios_base::out);
    
    if (!newFile.is_open()) {
        // Enviar respuesta error al cliente
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FILE_SERVICE_NAME;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FS_DECLARE_FILE;
        respuesta += SEPARADOR_CAMPO;
        respuesta += ERROR;
        respuesta += SEPARADOR_CAMPO;
        respuesta += "Fallo IO al declarar el archivo.";
        
        pMensajero->Responder(respuesta);
        
        return;
    }  
    
    newFile.close();
    
    // Envio de respuesta al cliente
    string respuesta = pComando[0]; // Token
    respuesta += SEPARADOR_CAMPO;
    respuesta += FILE_SERVICE_NAME;
    respuesta += SEPARADOR_CAMPO;
    respuesta += FS_DECLARE_FILE;
    respuesta += SEPARADOR_CAMPO;
    respuesta += OK;
    respuesta += SEPARADOR_CAMPO;
    respuesta += "Archivo declarado.";
    
    pMensajero->Responder(respuesta);
}

void FileService::putFileChunk(IMensajero* pMensajero, vector<string>& pComando) {
    // <token>|<FILE_SERVICE>|<PUT_CHUNK>|<file_name>|<nro chunk>|<chunk>
    
    string fileName = pComando[3] + string(".tmp");
    
    // Si el archivo no existe, se responde con error al cliente
    std::ifstream file((_pathAu + string("/") + fileName).c_str());
    
    if (!file) {
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FILE_SERVICE_NAME;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FS_PUT_CHUNK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pComando[4];       // Nro chunk
        respuesta += SEPARADOR_CAMPO;
        respuesta += ERROR;
        respuesta += SEPARADOR_CAMPO;
        respuesta += "Archivo no declarado.";
        pMensajero->Responder(respuesta);
        
        return;
    }
    
    file.close();    
    
    std:ofstream outputFile;
    outputFile.open((_pathAu + string("/") + fileName).c_str(), 
                            std::ios_base::binary | ios_base::out | ios::app);        
    
    // Para nuevos archivos, los chunks se van guardando siempre al final
    // del archivo
    outputFile.seekp(0, ios_base::end);
    
    // Decodifico chunk, obtentiendo su size
    TP::Codec codec;
    uint32_t sizeData;
    uint8_t* data;
    codec.decodificar(data, sizeData, pComando[5]);
    outputFile.write((char*)data, sizeData);
    outputFile.close();
    
    // Envio de respuesta al cliente
    string respuesta = pComando[0]; // Token
    respuesta += SEPARADOR_CAMPO;
    respuesta += FILE_SERVICE_NAME;
    respuesta += SEPARADOR_CAMPO;
    respuesta += FS_PUT_CHUNK;
    respuesta += SEPARADOR_CAMPO;
    respuesta += pComando[4];   // Nro chunk
    respuesta += SEPARADOR_CAMPO;
    respuesta += OK;
    respuesta += SEPARADOR_CAMPO;
    respuesta += "Chunk cargado correctamente.";
    
    pMensajero->Responder(respuesta);
}

void FileService::defineFile(IMensajero* pMensajero, vector<string>& pComando) {
    // <token>|<FILE_SERVICE>|<DEFINE_FILE>|<file_name>
    string fileName = pComando[3] ;
    
    int resultado = rename((_pathAu + string("/") + fileName + string(".tmp")).c_str(),
                           (_pathAu + string("/") + fileName).c_str());

    if (resultado == 0) {

        // Envio de respuesta al cliente
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FILE_SERVICE_NAME;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FS_DEFINE_FILE;
        respuesta += SEPARADOR_CAMPO;
        respuesta += OK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += "Archivo definido.";

        pMensajero->Responder(respuesta);
    } 
    else 
    {
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FILE_SERVICE_NAME;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FS_DEFINE_FILE;
        respuesta += SEPARADOR_CAMPO;
        respuesta += ERROR;
        respuesta += SEPARADOR_CAMPO;
        respuesta += "Error IO al definir el archivo.";
        
        pMensajero->Responder(respuesta);
    }
}
